package com.example.juc.sort;

import cn.hutool.core.thread.NamedThreadFactory;

import java.util.Arrays;
import java.util.concurrent.*;

public class CallableSort {

    private static final ExecutorService executorService = new ThreadPoolExecutor(
            5,
            10,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(10),
            new NamedThreadFactory("sort-multiThreadSort", false),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    private final int numberCount;
    private final int[] generateNumbers;
    private int[] singleThreadNumbers;
    private int[] multiThreadNumbers;

    public CallableSort(int numberCount) {
        this.numberCount = numberCount;
        this.generateNumbers = generateNums(numberCount);
        initData();
    }

    public CallableSort(int[] generateNumbers) {
        this.generateNumbers = generateNumbers;
        this.numberCount = generateNumbers.length;
        initData();
    }

    private void initData() {
        this.singleThreadNumbers = new int[numberCount];
        this.multiThreadNumbers = new int[numberCount];
        System.arraycopy(this.generateNumbers, 0, singleThreadNumbers, 0, numberCount);
        System.arraycopy(this.generateNumbers, 0, multiThreadNumbers, 0, numberCount);
    }

    public int[] getGenerateNumbers() {
        return generateNumbers;
    }

    /**
     * 生成待排序数据
     *
     * @param size
     * @return
     */
    public int[] generateNums(final int size) {
        int[] num = new int[size];
        for (int i = 0; i < size; i++) {
            num[i] = (int) (Math.random() * size * 2);

        }
        return num;
    }

    public int[] sort(int[] num) {
        Arrays.sort(num);
        return num;
    }

    /**
     * 合并两个数组
     *
     * @param ans
     * @param sub
     * @return
     */
    public int[] merge(int[] ans, int[] sub) {
        if (ans == null) {
            return sub;
        }
        int ansSize = ans.length;
        int subSize = sub.length;
        int[] result = new int[subSize + ansSize];
        for (int i = 0, ansIndex = 0, subIndex = 0; i < ansSize + subSize; i++) {
            if (subIndex >= subSize) {
                result[i] = ans[ansIndex++];
                continue;
            }
            if (ansIndex >= ansSize) {
                result[i] = sub[subIndex++];
                continue;
            }
            if (ans[ansIndex] < sub[subIndex]) {
                result[i] = ans[ansIndex++];
            } else {
                result[i] = sub[subIndex++];
            }
        }
        return result;
    }

    /**
     * 多线程排序
     *
     * @param groupSize 分组大小
     * @return 排序后数据
     */
    public int[] multiThreadSort(int groupSize) {
        CompletionService<int[]> completionService = new ExecutorCompletionService<>(executorService);
        // 一组以内，调用单线程排序
        if (numberCount <= groupSize || groupSize <= 0) {
            return sort(multiThreadNumbers);
        }
        // 将数组分割，按分割大小分成若干组，进行排序
        int subNum = (numberCount - 1) / groupSize + 1;
        for (int i = 0; i < subNum; i++) {
            int len = groupSize;
            if (i == subNum - 1) {
                len = numberCount - groupSize * i;
            }
            final int[] subNumbers = new int[len];
            System.arraycopy(multiThreadNumbers, i * groupSize, subNumbers, 0, len);
            Callable<int[]> task = new Callable<int[]>() {
                @Override
                public int[] call() {
                    return sort(subNumbers);
                }
            };
            completionService.submit(task);
        }
        int[] ans = null;
        // 开始对提交的排序任务的结果进行合并
        try {
            for (int i = 0; i < subNum; i++) {
                // get and remove the result
                Future<int[]> f = completionService.take();
                // 任务返回结果
                int[] tmp = f.get();
                // 合并到结果集
                ans = merge(ans, tmp);
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return ans;
    }

    public int[] singleThreadSort() {
        return this.sort(singleThreadNumbers);
    }
}
